import { defineStore } from "pinia"
import { ref, reactive } from "vue"
import Web3 from "web3"
import { isAddress } from "web3-validator"
import Token from "@/contracts/Token.json"
import Payment from "@/contracts/Payment.json"
import { ElNotification, ElLoading } from "element-plus"

const useAppStore = defineStore(
    "app",
    () => {
        // 钱包信息
        const address = ref("")
        const isConnect = ref(false)

        // web3/合约相关数据
        const dapp = reactive({
            loading: null,
            web3: null,

            // 注意这里要手动修改，使其与部署合约主网ID匹配
            // bsc测试网对应就是97
            chainId: 97,
            // token实例
            token: null,
            // 周期性支付合约实例
            payment: null,

            // 相关事件
            eventPaymentCreated: null,
            eventPaymentClaimed: null,
            eventPaymentToChanged: null,

            // 当前账号token余额
            tokenBalance: "-",
            // 当前账号ETH余额
            balance: "-",
            // 支付合约token余额
            contractTokenBalance: "-",

            // 发起支付表单数据
            formData: {
                token: "",
                to: "",
                effectiveDate: "",
                amount: "",
                frequency: "",
                unit: 0,
                allCount: ""
            },
            // token转账表单数据
            formDataToken: {
                to: "",
                amount: ""
            },

            // 我的订单列表
            toList: [],
            // 我发起的订单列表
            fromList: [],

            // 统计区域数据
            stats: {
                data1: 0,
                data2: 0,
                data3: 0,
                data4: 0,
                data5: 0
            }
        })

        // 默认tabs
        const tabsActive = ref("tabs1")
        // 频率单位名称列表
        const unitArr = ["秒", "天", "周", "月"]
        // 订单状态名称列表
        const statusArr = ["待生效", "生效中", "已结束"]

        // 钱包初始化
        const dappInit = async () => {
            try {
                // 检查环境
                if (typeof window.ethereum == "undefined") {
                    throw { code: -1, message: "请安装metamask或tokenpocket后刷新页面" }
                }

                // 监听钱包切换
                window.ethereum.on("accountsChanged", () => {
                    window.location.reload()
                })
                // 监听网络切换
                window.ethereum.on("chainChanged", () => {
                    window.location.reload()
                })

                // 检查钱包是否解锁
                window.ethereum._metamask.isUnlocked().then((res) => {
                    if (!res) throw { code: -1, message: "请解锁您的钱包" }
                })
                // 检查网络
                if (
                    parseInt(window.ethereum.chainId, 16) !== parseInt(dapp.chainId) &&
                    parseInt(window.ethereum.networkVersion) !== parseInt(dapp.chainId)
                ) {
                    throw { code: -1, message: "请将切换到BNB Smart Chain Testnet网络" }
                }

                // 获取当前钱包地址
                const accounts = await window.ethereum.request({ method: "eth_requestAccounts" })
                address.value = accounts[0]
                isConnect.value = true

                // 初始化web3
                // 谷歌浏览器安装metamask或tokenpocket后，他会自动向window注入web3对象
                // 前端只需要引入即可，实现和tokenpocket通信、交互
                dapp.web3 = new Web3(window.web3.currentProvider)
                // 实例化Token合约
                // 假设要测试其他人已部署的合约，而不是刚刚我们自己部署的，2个参数分别填abi字符串和合约地址字符串就行了
                dapp.token = new dapp.web3.eth.Contract(Token.abi, Token.networks[dapp.chainId].address)
                // 实例化Payment合约
                dapp.payment = new dapp.web3.eth.Contract(Payment.abi, Payment.networks[dapp.chainId].address)

                // 刷新2个table，主要是统计区域要用数据
                getToPayments()
                getFromPayments()

                // 设置支付表单模拟值
                setDemoValue()
                // 刷新余额
                getBalanceFunc()

                // 监听各个事件
                // 暂时暂停事件监听
                // await getEvents()
            } catch (error) {
                if (error.code == 4001) error.message = "用户拒绝连接钱包"
                if (error.code == -32002) error.message = "请打开metamask登录钱包账户"
                notice(`${error.message}`, false)
            }
        }

        // 设置支付表单模拟值
        const setDemoValue = () => {
            // ===============给表单赋值，测试的时候尽量不填，啊哈哈哈===============
            // token合约地址
            dapp.formData.token = dapp.token._address
            // 支付金额
            dapp.formData.amount = 1000
            // 接收者，我这里填自己方便测试，随便
            dapp.formData.to = address.value
            // 生效时间
            dapp.formData.effectiveDate = getDateTime()
            // 频率
            dapp.formData.frequency = 60
            // 单位（0秒 1天 2周 3月）
            dapp.formData.unit = 0
            // 支付次数
            dapp.formData.allCount = 20
            // ===============给表单赋值，测试的时候尽量不填，啊哈哈哈===============
        }

        // 获取余额
        const getBalanceFunc = async () => {
            // 获取token余额
            const tokenBalance = await dapp.token.methods.balanceOf(address.value).call()
            dapp.tokenBalance = tokenBalance ? Web3.utils.fromWei(tokenBalance, "ether") : 0
            // 获取eth余额
            const balance = await dapp.web3.eth.getBalance(address.value)
            dapp.balance = balance ? Web3.utils.fromWei(balance, "ether") : 0
            // 获取支付合约token余额
            const contractTokenBalance = await dapp.token.methods.balanceOf(dapp.payment._address).call()
            dapp.contractTokenBalance = contractTokenBalance ? Web3.utils.fromWei(contractTokenBalance, "ether") : 0
        }

        // 监听事件
        // 受json RPC服务器限制，监听事件会失败，象征性的创建了监听事件，但是会报错，报错内容：{code: -32005, message: 'limit exceeded'}
        // 刷新数据操作，已在相关函数后面进行了补偿处理
        const getEvents = async () => {
            // 监听订单创建事件
            dapp.eventPaymentCreated = await dapp.payment.events.PaymentCreated({}, (error, event) => {
                notice(`订单创建成功，交易hash:${event.transactionHash}`, true)
                // 刷新余额
                getBalanceFunc()
            })
            // 监听收益申领事件
            dapp.eventPaymentClaimed = await dapp.payment.events.PaymentClaimed({}, (error, event) => {
                notice(`收益申领成功，交易hash:${event.transactionHash}`, true)
                // 刷新列表
                getToPayments()
            })
            // 监听接收人变更事件
            dapp.eventPaymentToChanged = await dapp.payment.events.PaymentToChanged({}, (error, event) => {
                notice(`接收人变更成功，交易hash:${event.transactionHash}`, true)
                // 刷新列表
                getToPayments()
            })
        }

        // 取消事件监听
        const cancelEvents = async () => {
            await dapp.eventPaymentCreated.unsubscribe()
            await dapp.eventPaymentClaimed.unsubscribe()
            await dapp.eventPaymentToChanged.unsubscribe()
        }

        // 右上角提示
        const notice = (message, type) => {
            console.log(message)
            ElNotification({
                title: type ? "Success" : "Error",
                message,
                type: type ? "success" : "error"
            })
        }

        // 加载层
        const openLoading = () => {
            dapp.loading = ElLoading.service({
                lock: true,
                text: "Loading",
                background: "rgba(0, 0, 0, 0.3)"
            })
        }

        // 获取当前时间或将时间戳转为日期（Y-m-d H:i:s）
        const getDateTime = (timestamp = null) => {
            let now = new Date()
            if (timestamp) {
                now = new Date(timestamp * 1000)
            }
            const year = now.getFullYear()
            const month = (now.getMonth() + 1).toString().padStart(2, "0")
            const day = now.getDate().toString().padStart(2, "0")
            const hours = now.getHours().toString().padStart(2, "0")
            const minutes = now.getMinutes().toString().padStart(2, "0")
            const seconds = now.getSeconds().toString().padStart(2, "0")

            return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
        }

        // 最小生效时间，向前推5分钟，和合约代码对应，留给用户填写表单的时间
        const getPreviousFiveMinutes = () => {
            const now = new Date()
            now.setMinutes(now.getMinutes() - 5)
            const year = now.getFullYear()
            const month = (now.getMonth() + 1).toString().padStart(2, "0")
            const day = now.getDate().toString().padStart(2, "0")
            const hours = now.getHours().toString().padStart(2, "0")
            const minutes = now.getMinutes().toString().padStart(2, "0")
            const seconds = now.getSeconds().toString().padStart(2, "0")
            return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
        }

        // 发起支付
        const createPayment = async () => {
            try {
                openLoading()
                if (!isAddress(dapp.formData.token)) {
                    throw new Error("代币合约错误")
                }
                if (!isAddress(dapp.formData.to)) {
                    throw new Error("接收人钱包错误")
                }
                if (dapp.formData.amount <= 0) {
                    throw new Error("支付数量错误")
                }
                if (dapp.formData.allCount <= 0) {
                    throw new Error("支付次数错误")
                }
                if (dapp.formData.frequency <= 0) {
                    throw new Error("支付频率错误")
                }
                if (new Date(dapp.formData.effectiveDate).getTime() / 1000 + 300 < new Date().getTime() / 1000) {
                    throw new Error(`生效时间不能早于${getPreviousFiveMinutes()}`)
                }

                // 换算单位
                const amount = Web3.utils.toWei(dapp.formData.amount, "ether")
                // 检查token余额
                const tokenBalance = await dapp.token.methods.balanceOf(address.value).call()
                if (amount > tokenBalance) {
                    throw new Error(
                        `您的token余额不足，当前余额为: ${tokenBalance ? Web3.utils.fromWei(tokenBalance, "ether") : 0}`
                    )
                }

                // 查询授权额度（我给合约授权了多少token，不足时合约无法主动划扣token哦）
                const allowance = await dapp.token.methods.allowance(address.value, dapp.payment._address).call()

                // 检查授权金额
                if (amount > allowance) {
                    // 授权不足，就调用metamask或tokenpocket授权，授权给合约amount个token（授权后合约有权主动划转amount数量我的token）
                    await dapp.token.methods.approve(dapp.payment._address, amount).send({
                        from: address.value,
                        gasPrice: 5000000000
                    })
                }

                // 发起支付
                await dapp.payment.methods
                    .createPayment(
                        dapp.formData.token,
                        dapp.formData.to,
                        amount,
                        new Date(dapp.formData.effectiveDate).getTime() / 1000,
                        dapp.formData.frequency,
                        dapp.formData.unit,
                        dapp.formData.allCount
                    )
                    .send({
                        from: address.value,
                        gasPrice: 5000000000
                    })

                // 刷新余额
                await getBalanceFunc()
                // 跳转到我发起的
                tabsActive.value = "tabs2"
                // 刷新列表
                await getToPayments()
            } catch (error) {
                notice(error.message, false)
            } finally {
                dapp.loading.close()
            }
        }

        // 获取我的订单列表
        const getToPayments = async () => {
            try {
                // 获取数据
                const result = await dapp.payment.methods.getToPayments(address.value).call()
                if (result.length) {
                    let arr = []
                    let data5 = 0
                    result.map((v) => {
                        let status = 0
                        if (new Date().getTime() / 1000 > v.effectiveDate.toString(10)) {
                            status = 1
                        }
                        if (v.isCompleted) {
                            status = 2
                        }
                        arr.push({
                            id: v.id.toString(10),
                            token: v.token,
                            from: v.from,
                            to: v.to,
                            amount: Web3.utils.fromWei(v.amount, "ether"),
                            effectiveDate: getDateTime(v.effectiveDate.toString(10)),
                            frequency: v.frequency.toString(10),
                            frequencyName: v.frequency.toString(10) + unitArr[v.unit] + "/次",
                            allCount: v.allCount.toString(10),
                            allCountName: v.allCount.toString(10) + "次",
                            currentAmount: v.currentAmount ? Web3.utils.fromWei(v.currentAmount, "ether") : 0,
                            status: status,
                            remainingAmount: v.remainingAmount ? Web3.utils.fromWei(v.remainingAmount, "ether") : 0,
                            claimNumber: v.claimNumber.toString(10) + "次",
                            nextClaimTimes: v.nextClaimTimes ? getDateTime(v.nextClaimTimes.toString(10)) : "无",
                            averageAmount: (Web3.utils.fromWei(v.amount, "ether") / v.allCount.toString(10)).toFixed(8)
                        })

                        // 我支付的
                        data5++
                    })

                    // 我支付的-列表
                    dapp.toList = arr
                    // 我支付的-数量
                    dapp.stats.data5 = data5
                }
            } catch (error) {
                notice(error.message, false)
            }
        }

        // 获取我发起的列表
        const getFromPayments = async () => {
            try {
                // 获取数据
                const result = await dapp.payment.methods.getFromPayments(address.value).call()
                if (result.length) {
                    let arr = []
                    let data1 = 0,
                        data2 = 0,
                        data3 = 0,
                        data4 = 0
                    result.map((v) => {
                        // 判断订单状态
                        let status = 0
                        if (new Date().getTime() / 1000 > v.effectiveDate.toString(10)) {
                            status = 1
                        }
                        if (v.isCompleted) {
                            status = 2
                        }

                        // 统计数据
                        switch (status) {
                            case 0:
                                // 待生效
                                data2++
                                break
                            case 1:
                                // 生效中
                                data3++
                                break
                            case 2:
                                // 已结束
                                data4++
                                break
                        }
                        // 订单总数
                        data1++

                        arr.push({
                            id: v.id.toString(10),
                            token: v.token,
                            from: v.from,
                            to: v.to,
                            amount: Web3.utils.fromWei(v.amount, "ether"),
                            effectiveDate: getDateTime(v.effectiveDate.toString(10)),
                            frequency: v.frequency.toString(10),
                            frequencyName: v.frequency.toString(10) + unitArr[v.unit] + "/次",
                            allCount: v.allCount.toString(10),
                            allCountName: v.allCount.toString(10) + "次",
                            currentAmount: v.currentAmount ? Web3.utils.fromWei(v.currentAmount, "ether") : 0,
                            status: status,
                            remainingAmount: v.remainingAmount ? Web3.utils.fromWei(v.remainingAmount, "ether") : 0,
                            claimNumber: v.claimNumber.toString(10) + "次",
                            nextClaimTimes: v.nextClaimTimes ? getDateTime(v.nextClaimTimes.toString(10)) : "无",
                            averageAmount: (Web3.utils.fromWei(v.amount, "ether") / v.allCount.toString(10)).toFixed(8)
                        })
                    })

                    // 我的订单-列表
                    dapp.fromList = arr
                    // 统计数据
                    dapp.stats.data1 = data1
                    dapp.stats.data2 = data2
                    dapp.stats.data3 = data3
                    dapp.stats.data4 = data4
                }
            } catch (error) {
                notice(error.message, false)
            }
        }

        // 代币申领
        const releasePayment = async (_id) => {
            try {
                openLoading()
                if (_id <= 0) {
                    throw new Error("订单ID错误")
                }
                await dapp.payment.methods.releasePayment(parseInt(_id)).send({
                    from: address.value,
                    gasPrice: 5000000000
                })
                // 刷新列表，刷新后统计区域数据自动变化
                await getToPayments()
                await getFromPayments()
            } catch (error) {
                notice(error.message, false)
            } finally {
                dapp.loading.close()
            }
        }

        // 接受者转移
        const transferPayment = async (data) => {
            try {
                openLoading()
                if (data.id <= 0) {
                    throw new Error("订单ID错误")
                }
                if (!isAddress(data.newTo)) {
                    throw new Error("接受者地址错误")
                }
                await dapp.payment.methods.transferPayment(parseInt(data.id), data.newTo).send({
                    from: address.value,
                    gasPrice: 5000000000
                })
                // 刷新列表
                await getToPayments()
            } catch (error) {
                notice(error.message, false)
            } finally {
                dapp.loading.close()
            }
        }

        // 代币转账
        // 代币默认都在合约部署的那个钱包里面，可以派发一些给其他人
        const tokenTransfer = async () => {
            try {
                openLoading()
                if (!isAddress(dapp.formDataToken.to)) {
                    throw new Error("收款人地址错误")
                }
                if (dapp.formDataToken.amount <= 0) {
                    throw new Error("转账数量错误")
                }
                // 换算单位
                const amount = Web3.utils.toWei(dapp.formDataToken.amount, "ether")
                // 检查token余额
                const tokenBalance = await dapp.token.methods.balanceOf(address.value).call()
                if (amount > tokenBalance) {
                    throw new Error(
                        `您的token余额不足，当前余额为: ${tokenBalance ? Web3.utils.fromWei(tokenBalance, "ether") : 0}`
                    )
                }
                // 发起转账
                await dapp.token.methods
                    .transfer(dapp.formDataToken.to, Web3.utils.toWei(dapp.formDataToken.amount, "ether"))
                    .send({
                        from: address.value,
                        gasPrice: 5000000000
                    })
                // 刷新余额
                await getBalanceFunc()
            } catch (error) {
                notice(error.message, false)
            } finally {
                dapp.loading.close()
            }
        }

        return {
            dapp,
            address,
            isConnect,
            tabsActive,
            unitArr,
            statusArr,
            openLoading,
            dappInit,
            getBalanceFunc,
            notice,
            cancelEvents,
            createPayment,
            getToPayments,
            getFromPayments,
            releasePayment,
            transferPayment,
            tokenTransfer
        }
    },
    {
        persist: {
            storage: sessionStorage,
            // 持久化字段，就是刷新页面后tabs还是不变，有记忆
            paths: ["tabsActive"]
        }
    }
)

export default useAppStore
